#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/time.h>

#define PACKET_SIZE 4096
#define DEFAULT_PACKET_SIZE 64
#define DEFAULT_TTL 64

unsigned short checksum(void *b, int len) {
    unsigned short *buf = (unsigned short *)b;
    unsigned int sum = 0;
    unsigned short result;

    for (sum = 0; len > 1; len -= 2)
        sum += *buf++;
    if (len == 1)
        sum += *(unsigned char *)buf;
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    result = ~sum;
    return result;
}

double time_diff(struct timeval *t1, struct timeval *t2) {
    return (t2->tv_sec - t1->tv_sec) * 1000.0 + (t2->tv_usec - t1->tv_usec) / 1000.0;
}

int ping(const char *host, int count = 4) {
    int sockfd;
    struct sockaddr_in addr;
    struct hostent *h;
    char packet[DEFAULT_PACKET_SIZE];
    struct icmphdr *icmp;
    struct sockaddr_in from;
    socklen_t fromlen = sizeof(from);
    int bytes, seq = 0;
    struct timeval tv_out, tv_recv, tv_send;
    int packets_sent = 0, packets_received = 0;

    if ((h = gethostbyname(host)) == NULL) {
        std::cerr << "Unknown host: " << host << std::endl;
        return 1;
    }

    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
        perror("socket");
        return 1;
    }

    tv_out.tv_sec = 1;
    tv_out.tv_usec = 0;
    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out)) < 0) {
        perror("setsockopt");
        close(sockfd);
        return 1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = h->h_addrtype;
    addr.sin_port = 0;
    addr.sin_addr.s_addr = *(unsigned long *)h->h_addr;

    std::cout << "PING " << host << " (" << inet_ntoa(addr.sin_addr) << ") " << DEFAULT_PACKET_SIZE << " bytes of data." << std::endl;

    for (int i = 0; i < count; i++) {
        icmp = (struct icmphdr *)packet;
        icmp->type = ICMP_ECHO;
        icmp->code = 0;
        icmp->un.echo.id = getpid();
        icmp->un.echo.sequence = seq++;
        icmp->checksum = 0;
        icmp->checksum = checksum(icmp, sizeof(*icmp));

        gettimeofday(&tv_send, NULL);
        
        if (sendto(sockfd, packet, sizeof(*icmp), 0, (struct sockaddr *)&addr, sizeof(addr)) <= 0) {
            perror("sendto");
            continue;
        }
        packets_sent++;

        bytes = recvfrom(sockfd, packet, sizeof(packet), 0, (struct sockaddr *)&from, &fromlen);
        if (bytes < 0) {
            std::cout << "Request timeout for icmp_seq " << (seq - 1) << std::endl;
            continue;
        }

        gettimeofday(&tv_recv, NULL);
        packets_received++;

        double rtt = time_diff(&tv_send, &tv_recv);

        struct iphdr *ip = (struct iphdr *)packet;
        int iphdrlen = ip->ihl * 4;

        icmp = (struct icmphdr *)(packet + iphdrlen);
        
        std::cout << bytes << " bytes from " << inet_ntoa(from.sin_addr) << ": icmp_seq=" << icmp->un.echo.sequence 
                  << " ttl=" << (int)ip->ttl << " time=" << rtt << " ms" << std::endl;
        
        usleep(1000000);
    }

    close(sockfd);

    std::cout << "\n--- " << host << " ping statistics ---" << std::endl;
    std::cout << packets_sent << " packets transmitted, " << packets_received << " received, "
              << (100 * (packets_sent - packets_received) / packets_sent) << "% packet loss" << std::endl;

    return 0;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        std::cerr << "Usage: " << argv[0] << " <host> [count]" << std::endl;
        return 1;
    }

    int count = 4;
    if (argc > 2) {
        count = atoi(argv[2]);
    }

    return ping(argv[1], count);
}